#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <openssl/sha.h>
#include <dirent.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>

#define   FIRE_TYPE     3
#define   STATIC_TYPE   4
#define   DYNC_TYPE     5

#define MAX_CMDLINE_LEN     4096    
#define EVENT_SIZE        (sizeof (struct inotify_event))
#define EVENT_BUF_LEN     (1024 * (EVENT_SIZE + 16))
#define PROCESS_PATH   "./NexaGuard"
const char *program_name = "./NexaGuard"; // 要查找的程序名（不带路径）

int check_hash_file(const char *filename, pid_t pid,unsigned char type);

//计算hash值
void sha256_hash_file(const char *filename, unsigned char *hash)
{
    FILE *file = fopen(filename, "rb");
    SHA256_CTX sha256;
    SHA256_Init(&sha256);

    unsigned char buffer[1024];
    size_t bytesRead;
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
        SHA256_Update(&sha256, buffer, bytesRead);
    }

    SHA256_Final(hash, &sha256);
    fclose(file);
}
//发送度量值给BMC
void send_hash_to_bmc(const unsigned char *hash,unsigned char type)
{
    int cnt = 0;
    unsigned char metries_data[40] = { 0 };
    char metries_str[512] = { 0 };
    char command[1024] = { 0 };
    metries_data[cnt++] = FIRE_TYPE;//OS
    metries_data[cnt++] = type;//app
    metries_data[cnt++] = SHA256_DIGEST_LENGTH;//hash len
    memcpy(&metries_data[cnt],hash,SHA256_DIGEST_LENGTH);
    cnt += SHA256_DIGEST_LENGTH;
    metries_data[cnt++] = 1;//hash len
    metries_data[cnt++] = 1;//version
    for (int i = 0; i < cnt; i++) {
        sprintf(metries_str + i * 5, "0x%02x ", metries_data[i]);
    }
    snprintf(command,sizeof(command),"ipmitool raw 0x3c 0xf3 %s",metries_str);
    system(command);
}
//从BMC获取度量结果
int get_metrics_result(unsigned char type)
{
    char output[256] = { 0 };
    char command[1024] = { 0 };
    sprintf(command,"ipmitool raw 0x3c 0xf4 0x03 0x%02x",type);
    FILE* fp = popen(command, "r");
    if (fp == NULL) {
        perror("popen failed!");
        return -1;
    }

    char buffer[128];
    output[0] = '\0';
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        strcat(output, buffer);
    }
    // 关闭管道并返回输出
    pclose(fp);
    if(strstr(output,"01"))
        return 0;
    else
        return -1;
}

int get_pid_by_name(const char *name, pid_t *pid) 
{
    DIR *dir;
    struct dirent *entry;
    char path[1024];
    char cmdline[MAX_CMDLINE_LEN];
    FILE *fp;
    size_t len;
    char *token;

    *pid = -1; // 初始化PID为-1，表示未找到

    // 打开/proc目录
    dir = opendir("/proc");
    if (!dir) {
        perror("opendir");
        return -1;
    }

    // 遍历/proc目录下的每个子目录
    while ((entry = readdir(dir)) != NULL) {
        // 检查目录名是否为数字，表示进程ID
        if (isdigit(entry->d_name[0])) {
            // printf("entryname:%s\n",entry->d_name);
            // 构造/proc/[pid]/cmdline的路径
            snprintf(path, sizeof(path), "/proc/%s/cmdline", entry->d_name);
            // printf("path:%s\n",path);
            // 打开cmdline文件
            fp = fopen(path, "r");
            if (fp) {
                // 读取命令行参数
                if (fgets(cmdline, sizeof(cmdline), fp) != NULL) {
                    // 移除换行符，并将参数列表转换为以空格分隔的字符串
                    cmdline[strcspn(cmdline, "\n")] = '\0';
                    token = strtok(cmdline, "\0"); // strtok with '\0' as delimiter splits on null bytes

                    // 检查第一个参数（通常是程序名）是否匹配
                    // 注意：这里只比较了基本名，没有考虑路径
                    if (token && strcmp(token, name) == 0) {
                        // 找到匹配的进程，设置PID并退出循环
                        *pid = atoi(entry->d_name);
                        fclose(fp);
                        closedir(dir);
                        return 0;
                    }
                }
                fclose(fp);
            }
        }
    }

    closedir(dir);
    return -1; // 未找到匹配的进程
}

int process_exists(pid_t pid) {
    // 尝试向进程发送0信号，这个信号不会实际影响进程，只用于检查进程是否存在
    if (kill(pid, 0) == 0) {
        // 如果kill返回0，表示进程存在且当前用户有权限向其发送信号
        return 1; // 进程存在
    } else if (errno == ESRCH) {
        // 如果errno被设置为ESRCH，表示没有这样的进程
        return 0; // 进程不存在
    } else {
        // 其他错误情况，可能是权限不足等
        perror("kill"); // 打印错误信息
        return -1; // 无法确定进程是否存在（由于其他错误）
    }
}

int process_kill(pid_t pid)
{
    if(process_exists(pid) == 1)
    {
        if(kill(pid,SIGKILL) != 0)
        {
            return -1;
        }
    }
    return 0;
}

// 从/proc/pid/maps文件中获取代码段的内存范围
int get_code_segment_range(pid_t pid, unsigned long *start, unsigned long *end,int *cnt) {
    char maps_path[64];
    snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid);
    FILE *maps_file = fopen(maps_path, "r");
    if (maps_file == NULL) {
        perror("fopen");
        return -1;
    }
    char line[256];
    while (fgets(line, sizeof(line), maps_file)) {
        unsigned long seg_start, seg_end;
        char perms[5];
        if (sscanf(line, "%lx-%lx %4s", &seg_start, &seg_end, perms) == 3) {
            if (strchr(perms, 'x')) {  // 找到包含可执行代码段的内存区域  
                *start = seg_start;
                *end = seg_end;
                printf("maps section%d: 0x%lx - 0x%lx\n",*cnt,*start,*end);
                *cnt = *cnt + 1;
                start++;
                end++;
            }
        }
    }
    fclose(maps_file);
    if(*cnt == 0)
        return -1;
    return 0;  // 没有找到代码段
}

// 从/proc/pid/mem文件中读取进程的内存内容
int read_process_memory(pid_t pid, unsigned long start, unsigned long end, unsigned char **buffer, size_t *length) {
    char mem_path[64];
    snprintf(mem_path, sizeof(mem_path), "/proc/%d/mem", pid);
    FILE *mem_file = fopen(mem_path, "rb");
    if (mem_file == NULL) {
        perror("fopen");
        return -1;
    }

    // 移动到代码段起始地址
    if (fseek(mem_file, start, SEEK_SET) != 0) {
        perror("fseek");
        fclose(mem_file);
        return -1;
    }
    
    // 计算读取的长度
    *length = end - start;
    *buffer = malloc(*length);
    if (*buffer == NULL) {
        perror("malloc");
        fclose(mem_file);
        return -1;
    }

    // 读取内存内容
    if (fread(*buffer, 1, *length, mem_file) != *length) {
        perror("fread");
        free(*buffer);
        fclose(mem_file);
        return -1;
    }

    fclose(mem_file);
    return 0;
}

void init_metrics_value(const char *filename,unsigned char type)
{
    unsigned char hash[SHA256_DIGEST_LENGTH];
    sha256_hash_file(filename, hash);
    printf("hash len = %d,hash value = ",SHA256_DIGEST_LENGTH);
    for(int i=0;i<SHA256_DIGEST_LENGTH;i++)
    {
        printf("%x",hash[i]);
    }
    printf("\n");
    send_hash_to_bmc(hash,type);
}

unsigned long start[1024], end[1024];
int cnt = 0;
int dynamic_measure(pid_t pid)
{
    unsigned char *buffer;
    size_t length =0;
    memset(start,0,sizeof(start));
    memset(end,0,sizeof(end));
    cnt = 0;
    FILE *file = fopen("./mem","ab");
    if(file == NULL)
    {
        perror("无法打开文件");
        return -1;
    }
    fseek(file,0,SEEK_SET);
    ftruncate(fileno(file),0);
    if (get_code_segment_range(pid, start, end, &cnt) != 0) {
        fprintf(stderr, "Failed to find code segment for PID %d\n", pid);
        return -1;
    }
	if(access("./ecc",F_OK) != -1)
	{
		cnt--;
	}
    for(int i=0;i<cnt-1;i++)
    {
        if(read_process_memory(pid,start[i],end[i],&buffer,&length) != 0)
        {
            fprintf(stderr, "Failed to read process memory\n");
            return -1;
        }
        size_t written = fwrite(buffer,1,length,file);
        if(written != length)
        {
            perror("Error writing data to file");
            fclose(file);
            return -1;
        }
    }
    fclose(file);
    int result = check_hash_file("./mem",pid,DYNC_TYPE);
    if(result)
    {
        printf("Dynamic measurement failed,kill app");
        process_kill(pid);
        return -1;
    }
    printf("Dynamic measurement success\n");
    return 0;
}

int check_hash_file(const char *filename, pid_t pid,unsigned char type)
{
    unsigned char hash[SHA256_DIGEST_LENGTH];
    sha256_hash_file(filename, hash);
    if(type == 0)
    {
        printf("Send static metric values to BMC,with metric values of ");
    }
    else
    {
        printf("Send dynamic metric values to BMC,with metric values of ");
    }
    for(int i=0;i<SHA256_DIGEST_LENGTH;i++)
    {
        printf("%02x",hash[i]);
    }
    printf("\n");
    send_hash_to_bmc(hash,type);
    sleep(2);
    return get_metrics_result(type);
}

int main()
{
    int length, i = 0;
    int fd;
    int wd;
    char buffer[EVENT_BUF_LEN];
    int result = -1;
    pid_t pid;
    int timeout = 11;
    signal(SIGTSTP,SIG_IGN);
    while(1)
    {
        // Initialize inotify
        fd = inotify_init();
        if (fd < 0) {
            perror("inotify_init");
            exit(EXIT_FAILURE);
        }
        // Add watch for PROCESS_PATH
        wd = inotify_add_watch(fd, PROCESS_PATH, IN_ACCESS);

        if (wd == -1) {
            close(fd);
            sleep(1);
            continue;
        }
		timeout = 11;
        printf("In static measurement monitoring\n");
        i = 0;
        length = read(fd, buffer, EVENT_BUF_LEN);
        if (length < 0) {
            perror("read");
            break;
        }
        while (i < length) 
        {
            struct inotify_event *event = (struct inotify_event *) &buffer[i];
            if (event->mask & IN_ACCESS)
            {
                if(inotify_rm_watch(fd,wd) < 0)
                {
                    perror("inotify_rm_watch");
                }
                close(fd);
                if (get_pid_by_name(program_name, &pid) != 0) 
                {
                    break;
                }
                printf("Detected that the application has started runing\n");
                
                result = check_hash_file(PROCESS_PATH,pid,STATIC_TYPE);
                if(result)
                {
                    printf("Static measurement failed,kill app\n");
                    process_kill(pid);
                }
                else
                {
                    printf("Static measurement success\n");
                    printf("In dynamic measurement monitoring\n");
                    while(process_exists(pid) == 1)//检查pid是否存在
                    {
                        if(timeout++ > 10)
                        {
                            timeout = 0;
                            dynamic_measure(pid);
                        }
                        sleep(1);
                    }
                    printf("dynamic measurement End\n");
                }
                break;
            }
            i += EVENT_SIZE + event->len;
        }
    }
    return 0;
}